#include "remotenodewindow.h"

#include <QFile>
#include <QDir>
#include <QHeaderView>

RemoteNodeWindow::RemoteNodeWindow(QWidget *parent)
    : QWidget(parent)
{
    //TODO
    // Initialize all the pointers with NULL
    mainVLayout = NULL;
    nodesHLayout = NULL;
    buttonVLayout = NULL;
    tableVLayout = NULL;
    checkHLayout = NULL;
    nodeList = NULL;
    outputText = NULL;
    btnWakeOnLan = NULL;
    btnShutdown = NULL;
    btnRefresh = NULL;
    btnSelectAll = NULL;
    btnUnselectAll = NULL;
    wakeConfig = NULL;
    selectedNodes = new QList<int>;

    // Initialize the timer interval with zero
    wakeSeconds = 0;
    checkSeconds = 0;

    initWindow();
    readConfig();
}

RemoteNodeWindow::~RemoteNodeWindow()
{
    //TODO
    // Delete the instances on heap
    if (selectedNodes != NULL)    delete selectedNodes;
    if (wakeConfig != NULL)       delete wakeConfig;
}

void RemoteNodeWindow::initWindow()
{
    //TODO
    btnWakeOnLan = new QPushButton(tr("Wake on Lan"));
    btnShutdown = new QPushButton(tr("Shutdown"));
    btnRefresh = new QPushButton(tr("Get Status"));
    btnSelectAll = new QPushButton(tr("Select All"));
    btnUnselectAll = new QPushButton(tr("Unselect All"));
    outputText = new QTextEdit;
    outputText->setFixedHeight(200);

    // Initialize the nodeList
    nodeList = new QTableWidget;

    checkHLayout = new QHBoxLayout;
    checkHLayout->addWidget(btnSelectAll);
    checkHLayout->addWidget(btnUnselectAll);

    tableVLayout = new QVBoxLayout;
    tableVLayout->addWidget(nodeList);
    tableVLayout->addLayout(checkHLayout);

    buttonVLayout = new QVBoxLayout;
    buttonVLayout->addWidget(btnWakeOnLan);
    buttonVLayout->addWidget(btnShutdown);
    buttonVLayout->addWidget(btnRefresh);
#ifdef DEBUG
    //Check if the enabelWindow() function works
    QPushButton *testEnableButton = new QPushButton(tr("Press to test enable status"));
    buttonVLayout->addWidget(testEnableButton);
    connect(testEnableButton, SIGNAL(clicked()), this, SLOT(ontestEnableButton()));
#endif

    nodesHLayout = new QHBoxLayout;
    nodesHLayout->addLayout(tableVLayout);
    nodesHLayout->addLayout(buttonVLayout);

    mainVLayout = new QVBoxLayout;
    mainVLayout->addLayout(nodesHLayout);
    mainVLayout->addWidget(outputText);

    this->setLayout(mainVLayout);
    this->setFixedWidth(720);
    this->resize(720, 600);
}

void RemoteNodeWindow::enableWindow(bool status)
{
    //TODO
    nodeList->setEnabled(status);
    btnWakeOnLan->setEnabled(status);
    btnShutdown->setEnabled(status);
    btnRefresh->setEnabled(status);
    btnSelectAll->setEnabled(status);
    btnUnselectAll->setEnabled(status);
}

void RemoteNodeWindow::readConfig()
{
    //TODO
    // Read config from config file
    QDir::setCurrent(CONFIGPATH);
    wakeConfig = new QSettings(CONFILENAME, QSettings::IniFormat);
    wakeSeconds = wakeConfig->value("powerontimer").toInt();
    checkSeconds = wakeConfig->value("powerchecktimer").toInt();
    int rowSum = wakeConfig->value("nodenumber").toInt();
    wakeTimeout = wakeConfig->value("waketimeout").toInt();
    const QString nodesFilename = wakeConfig->value("nodelist").toString();

    // Set the column and row of the table
    nodeList->setColumnCount(COLUMNUM);
    nodeList->setRowCount(rowSum);
    // Set the column title of the table
    QStringList headList;
    headList.append("Host");
    headList.append("IP");
    headList.append("MAC");
    headList.append("Status");
    headList.append("Wake Time");
    nodeList->setHorizontalHeaderLabels(headList);
    // Set the width of every column of the table
    nodeList->horizontalHeader()->resizeSection(0, 80);
    nodeList->horizontalHeader()->resizeSection(1, 100);
    nodeList->horizontalHeader()->resizeSection(2, 130);
    nodeList->horizontalHeader()->resizeSection(3, 50);
    nodeList->horizontalHeader()->resizeSection(4, 90);

    // Output the content of config file for debug
#ifdef DEBUG
    qDebug() << "Reading " << CONFILENAME << ": the wake on LAN time interval is "
            << wakeSeconds << " seconds.";
    qDebug() << "Reading " << CONFILENAME << ": the check waking status time interval is "
            << checkSeconds << " seconds.";
    qDebug() << "Reading " << CONFILENAME << ": the name of nodelist file is "
            << nodesFilename << " seconds.";
#endif

    // Read the host name, MAC, and IP of the nodes from nodesFile
    QFile nodesFile(nodesFilename);
    if (!nodesFile.open(QIODevice::ReadOnly | QIODevice::Text))
        return;

    int i = 0;
    while (!nodesFile.atEnd()) {
        // Read a line
        QByteArray line = nodesFile.readLine();
        QString newLine(QString::fromAscii(line).trimmed());
#ifdef DEBUG
        qDebug() << "Reading " << nodesFilename << " Line " << i << ": " << newLine;
#endif
        QStringList newList = newLine.split(",", QString::SkipEmptyParts);
        // First colum: host name
        QTableWidgetItem *column1 = new QTableWidgetItem(newList.at(0));
        column1->setCheckState(Qt::Checked);
        nodeList->setItem(i, 0, column1);

        // Second column: IP address
        QTableWidgetItem *column2 = new QTableWidgetItem(newList.at(1));
        nodeList->setItem(i, 1, column2);

        // Third column: MAC address
        QTableWidgetItem *column3 = new QTableWidgetItem(newList.at(2));
        nodeList->setItem(i, 2, column3);

        // Forth colum: Waking status
        QTableWidgetItem *column4 = new QTableWidgetItem();
        nodeList->setItem(i, 3, column4);
        setWakeStatus(i, OFFLINETIP);

        // Fifth colum: Send wakeonlan time
        QTableWidgetItem *column5 = new QTableWidgetItem();
        column5->setText(tr(NOTWAKED));
        nodeList->setItem(i, 4, column5);

        i++;
    }
    // Connect the relational slots
    connect(btnSelectAll, SIGNAL(clicked()), this, SLOT(onBtnSelectAll()));
    connect(btnUnselectAll, SIGNAL(clicked()), this, SLOT(onBtnUnselectAll()));
    connect(btnRefresh, SIGNAL(clicked()), this, SLOT(onBtnRefresh()));
    connect(btnWakeOnLan, SIGNAL(clicked()), this, SLOT(onBtnWakeOnLan()));
    connect(btnShutdown, SIGNAL(clicked()), this, SLOT(onBtnShutdown()));
}

void RemoteNodeWindow::onBtnSelectAll()
{
    //TODO
    int rowSum = nodeList->rowCount();
    for (int i=0; i<rowSum; i++)
    {
        nodeList->item(i, 0)->setCheckState(Qt::Checked);
    }
}

void RemoteNodeWindow::onBtnUnselectAll()
{
    //TODO
    int rowSum = nodeList->rowCount();
    for (int i=0; i<rowSum; i++)
    {
        nodeList->item(i, 0)->setCheckState(Qt::Unchecked);
    }
}

void RemoteNodeWindow::onBtnRefresh()
{
    //TODO
    // Lock the editable parts in the window
    enableWindow(false);
    int nodeSum = nodeList->rowCount();    // Number of nodes in the table
    // Check the status of each node
    for (int i=0; i < nodeSum; i++)
    {
        bool status = checkWakeStatus(i);
        QString oldStatus =  nodeList->item(i, 3)->toolTip();
        if (oldStatus == ONLINETIP) {
            if (false == status) {
                setWakeStatus(i, OFFLINETIP);
            }
        } else if (oldStatus == OFFLINETIP) {
            if (true == status) {
                setWakeStatus(i, ONLINETIP);
            }
        } else if (oldStatus == STARTINGTIP) {
            if (true == status) {
                setWakeStatus(i, ONLINETIP);
            }
        }
    }
    // Unlock the editable parts in the window
    enableWindow(true);
}

void RemoteNodeWindow::onBtnWakeOnLan()
{
    //TODO
    // Lock the editable parts in the window
    enableWindow(false);
    checkCount = 0;

    // Start the global clock
    globalTime.start();

    int nodeSum = nodeList->rowCount();    // Number of nodes in the table
    // Append all the selected nodes to the list
    for (int i=0; i < nodeSum; i++)
    {
        if (nodeList->item(i, 0)->checkState() == Qt::Checked) {
            selectedNodes->append(i);
#ifdef DEBUG
            qDebug() << "Appending node " << nodeList->item(i, 0)->text() << " to selected node list";
#endif
        }
    }
    // Set timers
    wakeInterval.setInterval(wakeSeconds * 1000);
    checkInterval.setInterval(checkSeconds * 1000);

    // Connect the signals and slots
    connect(&wakeInterval, SIGNAL(timeout()), this, SLOT(onWakeInterval()));
    connect(&checkInterval, SIGNAL(timeout()), this, SLOT(onCheckInterval()));

    // Wake the first selected node at once
    onWakeInterval();

    // Start timers
    wakeInterval.start();
    checkInterval.start();
}

void RemoteNodeWindow::onWakeInterval()
{
    //TODO
    // Stop the timer
    wakeInterval.stop();
    // Get the number of selected nodes
    int selectedSum = selectedNodes->count();
    if (0 == selectedSum) {
        // If no node is selected
        disconnect(&wakeInterval, SIGNAL(timeout()), this, SLOT(onWakeInterval()));
    } else {
        int firstRow = selectedNodes->at(0);
        QString macAdd = nodeList->item(firstRow, 2)->text();

        // Send wakeonlan to the node
        char wakeCmdStr[1024];
        char *singleMac = macAdd.toLatin1().data();
        snprintf(wakeCmdStr, sizeof(wakeCmdStr), "wakeonlan %s", singleMac);
        system(wakeCmdStr);
//        delete singleMac;
        singleMac = NULL;

        QString command(tr("Send wakeonlan to node: %1 MAC Address: %2").arg(nodeList->item(firstRow, 0)->text()).arg(macAdd));
        outputText->append(command);
        setWakeStatus(firstRow, STARTINGTIP);
        // Record the number of milliseconds that have elasped since globalTime.start()
        nodeList->item(firstRow, 4)->setText(QString("%1").arg(globalTime.elapsed()));
        selectedNodes->removeAt(0);;
        wakeInterval.start();
    }
}

void RemoteNodeWindow::onCheckInterval()
{
    //TODO
    checkInterval.stop();
    checkCount ++;
    outputText->append(tr("Check the nodes status for Time %1").arg(checkCount));
#ifdef DEBUG
    qDebug() << tr("Check the nodes status for Time %1").arg(checkCount);
#endif
    // Check if 'wakeonlan' commands have been sent to all the selected nodes and no nodes is 'starting'
    bool noStarting = true;
    int nodeSum = nodeList->rowCount();
    for (int i=0; i < nodeSum; i++)
    {
        if (nodeList->item(i, 3)->toolTip() == STARTINGTIP) {
            noStarting = false;
            break;
        }
    }
    if (selectedNodes->count() == 0 && noStarting)
    {
        enableWindow(true);
    } else {
        // Check waking status
        for (int i=0; i < nodeSum; i++)
        {
            bool status = checkWakeStatus(i);
            if (status) {
                // the node is online
                QString oldStatus =  nodeList->item(i, 3)->toolTip();
                if (oldStatus != ONLINETIP)
                    nodeList->item(i, 4)->setText(globalTime.toString("hh:mm:ss"));
                setWakeStatus(i, ONLINETIP);
            } else {
                if (nodeList->item(i, 0)->checkState() == Qt::Checked &&
                    nodeList->item(i, 4)->text() != NOTWAKED) {
                    // If the node is selected and not waked yet
                    // Caculate the number of milliseconds that has elasped since the wakeonlan signal was sent to the node
#ifdef DEBUG
                    qDebug() << "WakeTimeout: " << wakeTimeout;
#endif
                    int wakeTime = nodeList->item(i, 4)->text().toInt();
                    int currentTime = globalTime.elapsed();
#ifdef DEBUG
                    qDebug() << "wakeTime: " << wakeTime;
                    qDebug() << "Current Time: " << currentTime;
#endif
                    int timeLag = (double) (currentTime - wakeTime) / (double) 1000;
#ifdef DEBUG
                    qDebug() << "timeLag: " << timeLag;
#endif
                    if (timeLag > wakeTimeout) {
                        setWakeStatus(i, OFFLINETIP);
                        nodeList->item(i, 4)->setText(TIMEOUT);
                        outputText->append(tr("Node %1 has been timeout").
                                           arg(nodeList->item(i,0)->text()));
                    }
                }
            }
        }
        checkInterval.start();
    }
}

void RemoteNodeWindow::onBtnShutdown()
{
    //TODO
    enableWindow(false);
    for (int i=0; i < nodeList->rowCount(); i++)
    {
        if (nodeList->item(i, 0)->checkState() == Qt::Checked)
        {
            QString hostName = nodeList->item(i, 0)->text();

            // Send shutdown to the node
            char shutdownCmdStr[1024];
            char *singleHostName = hostName.toLatin1().data();
            snprintf(shutdownCmdStr, sizeof(shutdownCmdStr),
                     "ssh %s sudo shutdown -P now", singleHostName);
            system(shutdownCmdStr);
//            delete singleHostName;
            singleHostName = NULL;

            QString command(tr("ssh %1 sudo shutdown -P now").arg(hostName));
            outputText->append(tr("Send command: %1").arg(command));
        }
    }
    enableWindow(true);
}

bool RemoteNodeWindow::checkWakeStatus(int nodeNum)
{
    //TODO
    QString nodeName(nodeList->item(nodeNum, 0)->text());
    // Combine the ssh command
    QString sshCommand(tr("ssh "));
    sshCommand.append(nodeName);

    // Send the ssh command and get the result string
    FILE *sshResultStream;
    char buf[1024];
    memset(buf, '/0', sizeof(buf));
    sshResultStream = popen(sshCommand.toLatin1().data(), "r");
    fread(buf, sizeof(char), sizeof(buf), sshResultStream);
    pclose(sshResultStream);

    // Check the first several characters of the result string to judge the node's status
    QString sshResultStr(buf);
    if (sshResultStr.startsWith("Linux")) return true;
    else return false;
#ifdef DEBUG
//    outputText->append(tr("Send command to %1: %2").arg(nodeName).arg(sshCommand));
    qDebug() << "Send command to " << nodeName << ": " << sshCommand;
//    int randNum = qrand();
//    if (randNum % 2 == 0) return true;
//    else return false;
#endif
}

void RemoteNodeWindow::setWakeStatus(int nodeNum, QString status)
{
    //TODO
    nodeList->item(nodeNum, 3)->setToolTip(status);
    if (ONLINETIP == status)    nodeList->item(nodeNum, 3)->setBackground(Qt::green);
    if (OFFLINETIP == status)    nodeList->item(nodeNum, 3)->setBackground(Qt::red);
    if (STARTINGTIP == status)    nodeList->item(nodeNum, 3)->setBackground(Qt::yellow);
}

#ifdef DEBUG
void RemoteNodeWindow::ontestEnableButton()
{
    //TODO
    if(btnWakeOnLan->isEnabled()) enableWindow(false);
    else enableWindow(true);
}
#endif
